package microprocessador;

import main.Main;
import unidadeControle.GeradorEndereco;
import unidadeControle.UnidadeControle;

import componentes.Multiplex;
import componentes.Registrador;

import excecao.ExcecaoFimPrograma;
import excecao.ExcecaoGeradorEndereco;
import excecao.ExcecaoMemoria;
import excecao.ExcecaoMemoriaControle;
import excecao.ExcecaoRegistrador;
import excecao.ExcecaoULA;

public class Processador {

	private final static char LE = '0';
	private final static char ESCREVE = '1';
	
	private Multiplex muxA;
	private Multiplex muxB;
	private Multiplex mux;
	private UnidadeControle unidadeControle;
	private Registrador[] R;
	private Registrador A;
	private Registrador regEndereco;
	private Registrador regInstrucao;
	private Registrador regDados;
	private Registrador PC;
	private Registrador SP;
	private ULA myULA;
	
	public Processador() {
		R = new Registrador[5];
		for (int i=0; i<5; i++){
			R[i] = new Registrador("R"+String.valueOf(i));
		}
		
		A = new Registrador("A");
		
		regEndereco = new Registrador("Rend");
		
		regInstrucao = new Registrador("Rinst");
		
		regDados = new Registrador("Rdados");
		
		PC = new Registrador("PC");
		
		SP = new Registrador("SP");
		
		muxA = new Multiplex(5);
		muxB = new Multiplex(5);
		mux = new Multiplex(2);
		myULA = new ULA();
		
		unidadeControle = new UnidadeControle();
	}
	
	public void simulaBIOS(String pc, String sp) throws ExcecaoRegistrador{
		// SP inicial
		SP.setRegistro(sp);
		
		// PC inicial
		PC.setRegistro(pc);
		
		// codigo da instrucao START
		regInstrucao.setRegistro("00000000000111100111");
		
		// primeira micro que deve ser executada
		unidadeControle.reseta("0");
	}
	
	public void executaMicro() throws ExcecaoULA, ExcecaoMemoria, ExcecaoGeradorEndereco, ExcecaoMemoriaControle, ExcecaoFimPrograma, ExcecaoRegistrador{
		// T1
		carregaMicro();
		// T2
		carregaMicro();
		// T3
		carregaMicro();		
	}
	
	public void executaInstrucao() throws ExcecaoULA, ExcecaoMemoria, ExcecaoGeradorEndereco, ExcecaoMemoriaControle, ExcecaoFimPrograma, ExcecaoRegistrador{
		while (true){
			executaMicro();
			if (GeradorEndereco.getInstancia().getPosicaoMicro() == 0){
				break;
			}
		}		
	}	
	
	public void executaPrograma() throws ExcecaoULA, ExcecaoMemoria, ExcecaoGeradorEndereco, ExcecaoMemoriaControle, ExcecaoFimPrograma, ExcecaoRegistrador{
		while (true){
			executaInstrucao();
		}		
	}	
		
	// Recebe a posicao da memoria de controle em que se encontram o array de controle associado a esta micro
	private void carregaMicro() throws ExcecaoULA, ExcecaoMemoria, ExcecaoGeradorEndereco, ExcecaoMemoriaControle, ExcecaoFimPrograma, ExcecaoRegistrador{ 
		String micro = unidadeControle.geraSinalControle(this.myULA.getFlags().getRegistro(), this.regInstrucao.getRegistro());
		
		if (micro == null){
			return;
		}
		
		char[] m = micro.toCharArray();
		char[] codOperacaoULA = new char[4];
		char[] codOperacaoMuxA = new char[3];
		char[] codOperacaoMuxB = new char[2];
		char[] codOperacaoMux = new char[1];
		
		// Carrega controle da ULA
		for(int i=0;i<4;i++)
			codOperacaoULA[i] = m[13+i];
		
		myULA.setCodOperacao(String.copyValueOf(codOperacaoULA));		
		
		// Carrega controle dos Registradores R
		for(int i=0;i<5;i++)
			R[i].setControle(m[i]);
		
		// Carrega controle do Acumulador
		A.setControle(m[5]);		
		
		// Carrega controle do PC
		PC.setControle(m[6]);
		
		// Carrega controle do Apontador de Pilha
		SP.setControle(m[7]);			
		
		// Carrega controle do Registrador de Endereço
		regEndereco.setControle(m[8]);
		
		// Carrega controle do Registrador de Dados
		regDados.setControle(m[9]);
		
		// Carrega controle do Registrador de Instrução
		regInstrucao.setControle(m[10]);
		
		// Carrega controle do Registrador de Flags
		myULA.getFlags().setControle(m[11]);			
		
		// Carrega controle da Memória Principal
		Main.getMemoriaPrincipal().setControle(m[12]);		
		
		// Carrega controle do Multiplexador A
		codOperacaoMuxA[0] = m[17];
		codOperacaoMuxA[1] = m[18];
		codOperacaoMuxA[2] = m[19];
		muxA.setSeletor(String.copyValueOf(codOperacaoMuxA));
		
		// Carrega controle do Multiplexador B
		codOperacaoMuxB[0] = m[20];
		codOperacaoMuxB[1] = m[21];
		muxB.setSeletor(String.copyValueOf(codOperacaoMuxB));
		
		// Carrega controle do Multiplexador MUX
		codOperacaoMux[0] = m[22];
		mux.setSeletor(String.copyValueOf(codOperacaoMux));
		
		executaMicroProcesso();
	}
	
	private void executaMicroProcesso() throws ExcecaoMemoria, ExcecaoRegistrador{
		
/*		          Bloco de leitura
		======================================
*/
		
		// Le Registrador de Flag da ULA 
		// nao e necessario pois ULA tem acesso direto a esse registrador 
		if(myULA.getFlags().getControle() == LE){}		
		
		//  Le Registradores R
		for(int i=0;i<5;i++){
			if(R[i].getControle() == LE){
				if(i == 0){
					muxA.setEntrada(2, R[i].getRegistro());
				}
				else{
					muxB.setEntrada(i-1, R[i].getRegistro());
				}
			}
		}
		
		//  Le Acumulador
		if(A.getControle() == LE){
			muxA.setEntrada(1, A.getRegistro());
		}
		
		//  Le PC
		if(PC.getControle() == LE){
			muxA.setEntrada(3, PC.getRegistro());
		}
		
		//  Le SP
		if(SP.getControle() == LE){
			muxA.setEntrada(4, SP.getRegistro());
		}				
		
		//  Le Registrador de Dados
		if(regDados.getControle() == LE){
			muxA.setEntrada(0, regDados.getRegistro());			
		}
		
		//  Le da Memória Principal		
		if(Main.getMemoriaPrincipal().getControle() == LE){
			mux.setEntrada(0, Main.getMemoriaPrincipal().getDado(regEndereco.getRegistro()));
		}
		
		myULA.setOperandoA(muxA.getSaida());
		myULA.setOperandoB(muxB.getSaida());
		
		myULA.executaInstrucao();
		
		
/*		         Bloco de Processamento
		======================================
		
		
		 Este Bloco eh feito automaticamente dentro de cada componente
		
		         Bloco de Escrita
		======================================
*/		
		
		// Escrita dos Registrador F
		if(myULA.getFlags().getControle() == ESCREVE){
			myULA.getFlags().setRegistro(myULA.getSaida());
		}	
		
		// Escrita dos Registradores R
		for(int i=0; i<5;i++){
			if(R[i].getControle() == ESCREVE){
				R[i].setRegistro(myULA.getSaida());
			}
		}
		
		// Escrita do Acumulador
		if(A.getControle() == ESCREVE){
			A.setRegistro(myULA.getSaida());
		}				
				
		// Escrita do PC
		if(PC.getControle() == ESCREVE){
			PC.setRegistro(myULA.getSaida());
		}
		
		// Escrita do SP
		if(SP.getControle() == ESCREVE){
			SP.setRegistro(myULA.getSaida());
		}		
		
		// Escrita do Registrador de Instruções
		if(regInstrucao.getControle() == ESCREVE){
			regInstrucao.setRegistro(myULA.getSaida());
		}
		
		// Escrita do Registrador de Endereço
		if(regEndereco.getControle() == ESCREVE){
			regEndereco.setRegistro(myULA.getSaida());
		}
		
		// MUX
		mux.setEntrada(1, myULA.getSaida());
		
		// Escrita do Registrador de Dados
		if(regDados.getControle() == ESCREVE){
			regDados.setRegistro(mux.getSaida());
		}
		
		//  Escrita da Memória Principal		
		if(Main.getMemoriaPrincipal().getControle() == ESCREVE){
			Main.getMemoriaPrincipal().setDado(regEndereco.getRegistro(), regDados.getRegistro());
		}		
	}

	public Registrador[] getR() {
		return R;
	}

	public Registrador getA() {
		return A;
	}

	public Registrador getRegEndereco() {
		return regEndereco;
	}

	public Registrador getRegInstrucao() {
		return regInstrucao;
	}

	public Registrador getRegDados() {
		return regDados;
	}

	public Registrador getPC() {
		return PC;
	}

	public UnidadeControle getUnidadeControle() {
		return unidadeControle;
	}

	public ULA getMyULA() {
		return myULA;
	}

	public Multiplex getMuxA() {
		return muxA;
	}

	public Multiplex getMuxB() {
		return muxB;
	}

	public Multiplex getMux() {
		return mux;
	}

	public Registrador getSP() {
		return SP;
	}
}
